﻿using System.Collections.Concurrent;

using ApproveFlowEngine.EntityFrameworkCore;
using ApproveFlowEngine.EntityFrameworkCoreSplitTable.Tools;
using ApproveFlowEngine.Split;

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;

namespace ApproveFlowEngine.EntityFrameworkCoreSplitTable.EFCore.Core
{
    public class SplitTableManager<TDbContext> : ISplitTableManager<TDbContext>, ITransientDependency where TDbContext : SplitDbContext
    {
        #region Private Fields

        private static readonly SemaphoreSlim _asyncInitLock = new SemaphoreSlim(1);
        private static readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1);
        private static readonly Dictionary<Type, string> _defaultTableNameMap = new Dictionary<Type, string>();
        private static readonly ConcurrentBag<string> _tableNames = new ConcurrentBag<string>();
        private static bool IsInit = false;
        private readonly IDistributedCache<List<string>> _cache;
        private readonly ICachedServiceProvider _cachedServiceProvider;
        private readonly IDbContextProvider<TDbContext> _dbContextProvider;
        private readonly ILogger<SplitTableManager<TDbContext>> _logger;

        #endregion Private Fields

        #region Public Constructors

        public SplitTableManager(IDistributedCache<List<string>> cache, ILogger<SplitTableManager<TDbContext>> logger, ICachedServiceProvider cachedServiceProvider, IDbContextProvider<TDbContext> dbContextProvider)
        {
            _cache = cache;
            _logger = logger;
            _cachedServiceProvider = cachedServiceProvider;
            _dbContextProvider = dbContextProvider;

            Init().Wait();
        }

        #endregion Public Constructors



        #region Private Methods

        private async Task Init()
        {
            if (!IsInit)
            {
                await _asyncInitLock.WaitAsync();
                try
                {
                    if (!IsInit)
                    {
                        await InitTableName();
                        IsInit = true;
                    }
                }
                finally
                {
                    _asyncInitLock.Release();
                }
            }
        }

        #endregion Private Methods



        #region Public Methods

        /// <summary>
        /// 检查并创建表
        /// </summary>
        /// <typeparam name="TEntity"> </typeparam>
        /// <param name="splitDbContext"> </param>
        /// <returns> </returns>
        public async Task CheckAndCreateTable<TEntity>() where TEntity : ISplitTable
        {
            var tableNames = await GetTableNames();
            var splitDbContext = await _dbContextProvider.GetDbContextAsync();
            var entityTypes = splitDbContext.Model.FindEntityTypes(typeof(TEntity));
            foreach (var entityType in entityTypes)
            {
                var tableName = entityType.GetTableName();
                if (tableName is not null)
                {
                    Console.WriteLine($"*************************{tableName}");
                    if (!tableNames.Contains(tableName))
                    {
                        await _asyncLock.WaitAsync();
                        try
                        {
                            tableNames = await GetTableNames();
                            if (!tableNames.Contains(tableName))
                            {
                                await CreateSplitTable<TEntity>(splitDbContext);
                            }
                        }
                        finally
                        {
                            _asyncLock.Release();
                        }

                        break;
                    }
                }
            }
        }

        /// <summary>
        /// 获取分表后缀
        /// </summary>
        /// <typeparam name="TEntity"> </typeparam>
        /// <param name="current"> </param>
        /// <returns> </returns>
        public async Task<string> GetSplitTableTail<TEntity>(DateTime current) where TEntity : ISplitTable
        {
            var splitTableTails = await GetSplitTableTails<TEntity>();
            var tail = SplitTableHelper.GetTail<TEntity>(current);
            if (splitTableTails.Contains(tail))
            {
                return tail;
            }
            return string.Empty;
        }

        /// <summary>
        /// 获取分表后缀
        /// </summary>
        /// <typeparam name="TEntity"> </typeparam>
        /// <param name="entity"> </param>
        /// <returns> </returns>
        public async Task<string> GetSplitTableTail<TEntity>(TEntity entity) where TEntity : ISplitTable
        {
            var splitTableTails = await GetSplitTableTails<TEntity>();
            var tail = entity.GetTail();
            if (splitTableTails.Contains(tail))
            {
                return tail;
            }
            return string.Empty;
        }

        /// <summary>
        /// 获取分表后缀
        /// </summary>
        /// <typeparam name="TEntity"> </typeparam>
        /// <returns> </returns>
        public async Task<List<string>> GetSplitTableTails<TEntity>() where TEntity : ISplitTable
        {
            var result = new List<string>();
            if (_defaultTableNameMap.TryGetValue(typeof(TEntity), out var defaultTableName))
            {
                var tableNames = await GetTableNames();
                result = tableNames.Where(c => c.StartsWith(defaultTableName))
                .Select(c => c.Replace(defaultTableName, string.Empty))
                .ToList();
            }
            return result;
        }

        /// <summary>
        /// 获取分表后缀
        /// </summary>
        /// <typeparam name="TEntity"> </typeparam>
        /// <param name="begin"> </param>
        /// <param name="end"> </param>
        /// <returns> </returns>
        public async Task<List<string>> GetSplitTableTails<TEntity>(DateTime? begin = null, DateTime? end = null) where TEntity : ISplitTable
        {
            var splitTableTails = await GetSplitTableTails<TEntity>();
            var validateTableTails = SplitTableHelper.GetTails<TEntity>(splitTableTails, begin, end);
            return validateTableTails;
        }

        /// <summary>
        /// 获取表名
        /// </summary>
        /// <returns> </returns>
        public async Task<List<string>> GetTableNames()
        {
            return _tableNames.ToList();
        }

        /// <summary>
        /// 获取有效表后缀
        /// </summary>
        /// <typeparam name="TEntity"> </typeparam>
        /// <param name="tails"> </param>
        /// <returns> </returns>
        public async Task<List<string>> GetValidTails<TEntity>(IEnumerable<string> tails) where TEntity : ISplitTable
        {
            var splitTableTails = await GetSplitTableTails<TEntity>();
            var validTails = tails.Intersect(splitTableTails).ToList();
            return validTails;
        }

        #endregion Public Methods

        #region Private Methods

        /// <summary>
        /// 创建表
        /// </summary>
        /// <typeparam name="TEntity"> </typeparam>
        /// <param name="splitDbContext"> </param>
        /// <returns> </returns>
        private async Task CreateSplitTable<TEntity>(SplitDbContext splitDbContext) where TEntity : ISplitTable
        {
            await CreateSplitTable(splitDbContext, typeof(TEntity));
        }

        /// <summary>
        /// 创建表
        /// </summary>
        /// <param name="splitDbContext"> 分表上下文 </param>
        /// <param name="type"> </param>
        /// <returns> </returns>
        private async Task CreateSplitTable(SplitDbContext splitDbContext, Type type)
        {
            var relations = GetCreateTableRelation(splitDbContext, type);
            await CreateTable(relations);
        }

        /// <summary>
        /// 根据关系创建表
        /// </summary>
        /// <param name="relations"> 表关系 </param>
        /// <returns> </returns>
        private async Task CreateTable(List<KeyValuePair<(string, string), Table>> relations)
        {
            if (relations is null || relations.Count is 0)
            {
                return;
            }
            TableTailHelper.CreatingTable(true);
            var createDbContex = _cachedServiceProvider.GetRequiredService<TDbContext>();
            var createRelationalModel = createDbContex.GetService<IDesignTimeModel>().Model.GetRelationalModel() as RelationalModel;
            if (createRelationalModel is not null)
            {
                createRelationalModel.DefaultTables.Clear();
                createRelationalModel.Tables.Clear();
                foreach (var item in relations)
                {
                    createRelationalModel.Tables.Add(item.Key, item.Value);
                }
            }
            var databaseCreator = createDbContex.Database.GetService<IDatabaseCreator>() as RelationalDatabaseCreator;
            try
            {
                await databaseCreator.CreateTablesAsync();
            }
            catch (Exception ex)
            {
                var tableNames = relations.Select(c => c.Key.Item1).ToList();
                _logger.LogWarning(ex,
                                    $"create table error, table name:[{string.Join(',', tableNames)}].");
            }
            finally
            {
                TableTailHelper.CreatingTable(false);
                foreach (var table in relations)
                {
                    _tableNames.Add(table.Key.Item1);
                }
            }
        }

        private List<KeyValuePair<(string, string), Table>> GetCreateTableRelation(SplitDbContext dbContext,
                            Type shadingType)
        {
            var contextModel = dbContext.GetService<IDesignTimeModel>().Model;
            var entityTypes = contextModel.GetEntityTypes();
            var contextModelRelationalModel = contextModel.GetRelationalModel() as RelationalModel;
            var valueTuples = contextModelRelationalModel?.Tables
                         .Where(o => o.Value.EntityTypeMappings.All(m => m.TypeBase.ClrType == shadingType))
                         .ToList();
            return valueTuples;
        }

        /// <summary>
        /// 初始化表字典
        /// </summary>
        /// <returns> </returns>
        private async Task InitTableName()
        {
            var initDbContext = _cachedServiceProvider.GetRequiredService<TDbContext>();
            var entityTypes = initDbContext.Model.GetEntityTypes();
            foreach (var entityType in entityTypes)
            {
                _defaultTableNameMap.TryAdd(entityType.ClrType, entityType.GetTableName());
            }
            var dbConn = initDbContext.Database.GetDbConnection();
            var allTables = await dbConn.GetSchemaAsync("Tables", new string[] { null, dbConn.Database });
            var tableNames = allTables.Select().Select(c => c[2].ToString()!).ToList();
            foreach (var tableName in tableNames)
            {
                _tableNames.Add(tableName);
            }
        }

        #endregion Private Methods
    }
}